package tencent

import (
	"context"
	"log/slog"
	"net/http"
	"net/url"
	"os"
	"strings"
	"time"

	"gitee.com/gxcc/go-utils/utils"
	"github.com/pkg/errors"
	"github.com/tencentyun/cos-go-sdk-v5"
)

type CosSvc struct {
	SecretId  string
	SecretKey string
	BucketUrl string
	cosClient *cos.Client
}

func (svc *CosSvc) GetClient() *cos.Client {
	if svc.cosClient == nil {
		u, _ := url.Parse(svc.BucketUrl)
		b := &cos.BaseURL{BucketURL: u}
		svc.cosClient = cos.NewClient(b, &http.Client{
			Transport: &cos.AuthorizationTransport{
				SecretID:  svc.SecretId,
				SecretKey: svc.SecretKey,
			},
		})
	}
	return svc.cosClient
}

func (svc *CosSvc) UploadFile(objectKey string, filePath string) error {
	if _, err := os.Stat(filePath); err != nil {
		slog.Error(err.Error())
		return err
	}

	client := svc.GetClient()
	if client == nil {
		err := errors.New("client is nil")
		slog.Error(err.Error())
		return err
	}

	_, _, err := client.Object.Upload(
		context.Background(), objectKey, filePath, nil,
	)
	if err != nil {
		slog.Error(err.Error())
		return err
	}

	return nil
}

func (svc *CosSvc) DownloadFile(objectKey string, filePath string) error {
	client := svc.GetClient()
	if client == nil {
		err := errors.New("client is nil")
		slog.Error(err.Error())
		return err
	}

	_, err := client.Object.Download(
		context.Background(), objectKey, filePath, &cos.MultiDownloadOptions{
			ThreadPoolSize: 5,
		},
	)
	if err != nil {
		slog.Error(err.Error())
		return err
	}
	return nil
}

func (svc *CosSvc) CheckFileExist(objectKey string) (bool, error) {
	client := svc.GetClient()
	if client == nil {
		err := errors.New("client is nil")
		slog.Error(err.Error())
		return false, err
	}

	ok, err := client.Object.IsExist(context.Background(), objectKey)
	if err != nil {
		slog.Error("head object failed: %v\n", err)
		return false, err
	} else {
		return ok, nil
	}
}

func (svc *CosSvc) CopyFile(sourceUrl string, dstKey string) error {
	client := svc.GetClient()
	if client == nil {
		err := errors.New("client is nil")
		slog.Error(err.Error())
		return err
	}

	// sourceUrl处理
	if strings.HasPrefix(sourceUrl, "https://") {
		sourceUrl = sourceUrl[len("https://"):]
	} else if strings.HasPrefix(sourceUrl, "http://") {
		sourceUrl = sourceUrl[len("http://"):]
	}

	_, _, err := client.Object.Copy(context.Background(), dstKey, sourceUrl, nil)
	if err != nil {
		slog.Error(err.Error())
		return err
	}

	return nil
}

func (svc *CosSvc) RenameFile(srcKey string, dstKey string) error {
	client := svc.GetClient()
	if client == nil {
		err := errors.New("client is nil")
		slog.Error(err.Error())
		return err
	}

	var separate = utils.If(strings.HasSuffix(svc.BucketUrl, "/"), "", "/")
	var sourceUrl = svc.BucketUrl + separate + srcKey
	if strings.HasPrefix(sourceUrl, "https://") {
		sourceUrl = sourceUrl[len("https://"):]
	} else if strings.HasPrefix(sourceUrl, "http://") {
		sourceUrl = sourceUrl[len("http://"):]
	}
	slog.Debug("sourceUrl: " + sourceUrl)

	_, _, err := client.Object.Copy(context.Background(), dstKey, sourceUrl, nil)
	if err != nil {
		slog.Error(err.Error())
		return err
	}

	_, err = client.Object.Delete(context.Background(), srcKey, nil)
	if err != nil {
		slog.Error(err.Error())
		return err
	}

	return nil
}

func (svc *CosSvc) DeleteFile(objectKey string) error {
	client := svc.GetClient()
	if client == nil {
		err := errors.New("client is nil")
		slog.Error(err.Error())
		return err
	}

	_, err := client.Object.Delete(context.Background(), objectKey, nil)
	if err != nil {
		slog.Error(err.Error())
		return err
	}
	return nil
}

func (svc *CosSvc) DeleteDirectory(directory string) error {
	client := svc.GetClient()
	if client == nil {
		err := errors.New("client is nil")
		slog.Error(err.Error())
		return err
	}

	if !strings.HasSuffix(directory, "/") {
		directory += "/"
	}

	marker := ""
	isTruncated := true
	for isTruncated {
		v, _, err := client.Bucket.Get(context.Background(), &cos.BucketGetOptions{
			Prefix:    directory, // prefix 表示要查询的文件夹
			Delimiter: "",        // deliter 表示分隔符, 设置为/表示列出当前目录下的 object, 设置为空表示列出所有的 object
			MaxKeys:   1000,      // 设置最大遍历出多少个对象, 一次 listobject 最大支持1000
			Marker:    marker,
		})
		if err != nil {
			slog.Error(err.Error())
			return err
		}

		for _, content := range v.Contents {
			slog.Debug("Object: %v\n", content.Key)
			_, err := client.Object.Delete(context.Background(), content.Key, nil)
			if err != nil {
				slog.Error(err.Error())
				return err
			}
		}
		isTruncated = v.IsTruncated // 是否还有数据
		marker = v.NextMarker       // 设置下次请求的起始 key
	}

	return nil
}

func (svc *CosSvc) GenSignedDownloadUrl(objectKey string, expireTime time.Duration) (string, error) {
	client := svc.GetClient()
	if client == nil {
		err := errors.New("client is nil")
		slog.Error(err.Error())
		return "", err
	}

	presignedURL, err := client.Object.GetPresignedURL(context.Background(), http.MethodGet, objectKey,
		svc.SecretId, svc.SecretKey, expireTime, nil)
	if err != nil {
		slog.Error(err.Error())
		return "", nil
	}

	return presignedURL.String(), nil
}

func (svc *CosSvc) GenSignedUploadUrl(objectKey string, expireTime time.Duration) (string, error) {
	client := svc.GetClient()
	if client == nil {
		err := errors.New("client is nil")
		slog.Error(err.Error())
		return "", err
	}

	presignedURL, err := client.Object.GetPresignedURL(context.Background(), http.MethodPut, objectKey,
		svc.SecretId, svc.SecretKey, expireTime, nil)
	if err != nil {
		slog.Error(err.Error())
		return "", nil
	}

	return presignedURL.String(), nil
}
